#include <algorithm>
#include <math.h>

#include "QueryHandler.h"

#define EMPTY_OPERATOR ("_empty")

QueryHandler::QueryHandler()
{
}

Query *QueryHandler::loadQueryFromFile(std::string filename)
{
	Query *q = 0;

	std::ifstream ifile(filename.c_str());

	if (!ifile)
	{
		return q;
	}
		
	Json::Reader reader;
	Json::Value root;

	bool read = reader.parse(ifile, root);

	ifile.close();

	if (read)
	{
		Json::Value::iterator i = root.begin();
		parseOpsfromInput(*i);		
		i++;
		q = parseQueryfromInput(*i, q);
	}	

	return q;
}

bool QueryHandler::printQuery(std::string filename, Query *q)
{
	std::ofstream ofile(filename.c_str());

	Json::Value root;
	
	printQuery(root, q);

	ofile << root;

	ofile.close();

	return true;
}

bool QueryHandler::checkSwap(std::vector <OperatorNode *> &opsList, std::vector <OperatorNode *> &opsListPrev)
{
	std::pair<std::vector <OperatorNode *> :: iterator, std::vector <OperatorNode *> :: iterator> p = std::mismatch(opsList.begin(), opsList.end(), opsListPrev.begin());

	while (p.first != opsList.end())
	{
		if ((*p.first)->op->getName() == (*p.second)->op->getName())
		{
			p.first++;
			p.second++;

			continue;
		}

		if (!checkSwap((*p.first)->op, (*p.second)->op))
		{
			return false;
		}

		p.first++;
		p.second++;
	}

	return true;
}

static bool sort_by_name(OperatorNode *op1, OperatorNode *op2)
{
	if (op1->op->getName() < op2->op->getName())
	{
		return true;
	}

	return false;
}

void QueryHandler::search(Query *q)
{
	std::string filename = "output_search.txt";	
	std::vector <OperatorNode *> opsList;
	Json::Value root;

	getOpsList(opsList, q);	
	std::vector <OperatorNode *> opsListPrev(opsList.size());

	std::copy(opsList.begin(), opsList.end(), opsListPrev.begin());
	std::sort(opsList.begin(), opsList.end(), sort_by_name);

	int i = 0;
	while (std::next_permutation(opsList.begin(), opsList.end(), sort_by_name))
	{
		if (checkSwap(opsList, opsListPrev))
		{			
			setOpsList(opsList.begin(), q);

			checkIdempotency(opsList, q);

			std::string str;
			sprintf(const_cast<char *>(str.c_str()), "gen_%.10d", i);

			printQuery(root[str.c_str()]["query"], q);
			root[str.c_str()]["cost"]["->"] = "end";
		}
		i++;
	}

	std::ofstream ofile(filename.c_str());
	ofile << root;
	ofile.close();
}

void QueryHandler::parseOpsfromInput(Json::Value &root)
{
	Operators.push_back(new Operator(EMPTY_OPERATOR));

	for (Json::Value::iterator i = root.begin(); i != root.end(); i++)
	{
		if ((*i).isObject())
		{
			Operators.push_back(new Operator(i.memberName()));			
		}
	}

	for (Json::Value::iterator i = root.begin(); i != root.end(); i++)
	{
		if ((*i).isObject())
		{			
			Operator *op = findOperatorbyName(i.memberName());

			if ((*i)["commutativity"] == true)
			{
				op->setOpProperties(LOGICAL_COMMUTATIVITY);

				Json::Value ops = (*i)["opsComm"];

				std::vector <Operator *> opsComm;
				for (Json::Value::iterator i = ops.begin(); i != ops.end(); i++)
				{
					opsComm.push_back(findOperatorbyName((*i).asString()));
				}

				op->addCommOps(opsComm);
			}

			if ((*i)["idempotency"] == true)
			{
				op->setOpProperties(LOGICAL_IDEMPOTENCY);
			}

			if ((*i)["not_movable"] == true)
			{
				op->setOpProperties(NOT_MOVABLE);
			}

			//NOTE: Add here more properties.
		}
	}
}

Operator *QueryHandler::findOperatorbyName(std::string name)
{
	for (std::vector <Operator *> :: iterator i = Operators.begin(); i != Operators.end(); i++)
	{
		if ((*i)->getName() == name)
		{
			return *i;
		}
	}

	return 0;
}

Query *QueryHandler::parseQueryfromInput(Json::Value &root, Query *q)
{
	int curr = 0;
	for (Json::Value::iterator i = root.begin(); i != root.end(); i++)
	{
		if ((*i).isObject())
		{
			Query *t;
			if (!q)
			{
				q = new Query(findOperatorbyName(i.memberName()));
				t = q;
			}
			else
			{
				q->addOperator(findOperatorbyName(i.memberName()));
				t = q->lists[curr++];
			}

			parseQueryfromInput(*i, t);
			parseCost(*i, t);
		}
	}

	return q;
}

void QueryHandler::getOpsList(std::vector <OperatorNode *> &opsList, Query *q)
{
	opsList.push_back(q->node);

	for (unsigned int i = 0; i < q->lists.size(); i++)
	{
		getOpsList(opsList, q->lists[i]);
	}
}

void QueryHandler::setOpsList(std::vector <OperatorNode *> :: iterator it, Query *q)
{
	q->node->op = (*it)->op;

	for (unsigned int i = 0; i < q->lists.size(); i++)
	{
		setOpsList(++it, q->lists[i]);
	}
}

void QueryHandler::printQuery(Json::Value &node, Query *q)
{
	node[q->node->op->getName()] = Json::Value();

	if (q->lists.size() == 0)
	{
		node[q->node->op->getName()]["->"] = "end";
	}

	for (unsigned int i = 0; i < q->lists.size(); i++)
	{
		printQuery(node[q->node->op->getName()], q->lists[i]);
	}
}

bool QueryHandler::checkSwap(Operator *op1, Operator *op2)
{
	std::vector <std::pair<Operator *, Operator *> > :: iterator f;
	std::pair<Operator *, Operator *> fwd = std::make_pair(op1, op2);

	f = find(banned_permutations.begin(), banned_permutations.end(), fwd);

	if (f != banned_permutations.end())
	{
		return false;
	}

	if (checkMovable(op1) || checkMovable(op2))
	{
		ban_pair(fwd);
		return false;
	}

	if (checkCommutativity(op1, op2))
	{
		return true;
	}

	ban_pair(fwd);

	return false;
}

void QueryHandler::ban_pair(std::pair<Operator *, Operator *> p)
{
	banned_permutations.push_back(p);
	banned_permutations.push_back(std::make_pair(p.second, p.first));
}

bool QueryHandler::checkCommutativity(Operator *op1, Operator *op2)
{
	return op1->findCommOp(op2) || op2->findCommOp(op1);
}

bool QueryHandler::checkMovable(Operator *op)
{
	return (op->getOpProperties() & NOT_MOVABLE) == NOT_MOVABLE;
}

void QueryHandler::checkIdempotency(std::vector <OperatorNode *> &opsList, Query *q)
{
	std::vector <OperatorNode *> t(opsList);
	std::vector <OperatorNode *> :: iterator it = opsList.begin();

	while ((it = std::adjacent_find(it, opsList.end())) != opsList.end())
	{
		if (checkIdempotency((*it)->op))
		{
			if (checkIdempotency((*it)->op, q))
			{
				(*it)->op = findOperatorbyName(EMPTY_OPERATOR);
			}
		}		
	}

	setOpsList(opsList.begin(), q);
	opsList = t;
}

bool QueryHandler::checkIdempotency(Operator *op, Query *q)
{
	if (op->getName() == q->node->op->getName())
	{
		return q->lists.size() == 1;
	}

	for (unsigned int i = 0; i < q->lists.size(); i++)
	{
		if (!checkIdempotency(op, q->lists[i]))
		{
			return false;
		}
	}

	return true;
}

bool QueryHandler::checkIdempotency(Operator *op)
{
	return (op->getOpProperties() & LOGICAL_IDEMPOTENCY) == LOGICAL_IDEMPOTENCY;
}

void QueryHandler::parseCost(Json::Value &root, Query *q)
{
	Json::Value::Members m = root.getMemberNames();	

	std::vector <int> dimensions;
	double coefficient = 1;
	double time = 0;
	double empty = 0;

	if (std::find(m.begin(), m.end(), "coefficient") != m.end())
	{
		coefficient = root["coefficient"].asDouble();
	}

	if (std::find(m.begin(), m.end(), "time") != m.end())
	{
		time = root["time"].asDouble();
	}

	if (std::find(m.begin(), m.end(), "empty") != m.end())
	{
		empty = root["empty"].asDouble();
	}

	if (std::find(m.begin(), m.end(), "count") != m.end())
	{
		for (Json::Value::iterator i = root["count"].begin(); i != root["count"].end(); i++)
		{
			dimensions.push_back((*i).asInt());
		}
	}

	q->node->cost = new OperatorCost(dimensions, empty, time, coefficient);
}